Repository navigation
Conversation
🤖 CI reportℹ️ MCP agent API — agent-facing tool changesWhat this PR changes for agents, from the tool schema snapshots and tool definitions. Tools added (5): |
Silthus
force-pushed
the
feat/email-domain-agent-setup
branch
from
October 7, 2026 11:46
3ed23b2 to
8cbc657
Compare
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Feature flags:
workflows-email-domain-agent-setup. Default off; this handoff does not activate them.Warning
🟡 Merge risk: medium (merge-risk rubric)
workflows-email-domain-agent-setupis on. The web app's payloads match the serializers and tests cover each endpoint. Provider changes already made persist after a revert.Fork handoff for source #109703. Modernized head
8cbc65746ebc271925c464070c2bd165bb23cf19. This draft prepares review and recreation in the upstream repository.Changes
Start with
posthog/api/integration.pyandposthog/api/integration_domain_serializers.py.workflows-email-domain-agent-setupflag. The backend checks the same flag, so turning it on enables the tools and the API access they need together:integrations-email-createintegrations-email-verify-createintegrations-domain-connect-check-retrieveintegrations-domain-connect-apply-url-createintegrations-email-partial-update_dmarc, then verify untilsuccess.setting-up-an-email-domainskill teaches that job, including which records to merge (SPF, existing DMARC) instead of duplicating.integration:read). Verify, apply-url and email update write (integration:write), because verify and apply-url call SES.attr.contextandprovider_endpointerrors on apply-url can come back together instead of one at a time.integrations-createstays disabled. It accepts an untypedconfigfor 30+ kinds, including credentials.integrations-email-createreuses the samePOST integrations/route withkind: emailinjected and the typed email config as its only input.providertoses. The API still requires it.context: email, hidescontextandproxy_record_id, and requiresintegration_id. The API accepts reverse proxy context only for sessions. Scoped API keys and OAuth receive 403 before signing a proxy URL.email/verifyandPATCH emailon a non-email integration now return 400 instead of 500.apply-urlnow returns 404 for an unknown or non-emailintegration_id, and for an unknownproxy_record_idfrom a session. Scoped proxy requests receive 403. Before, a stale id returned 500, and a non-email id returned a "try again later" 400 and reached error tracking.PATCH emailwith a different address returns 400. With it off, it still returns 200 and keeps the old address. The check ignores case, so a sender stored with capitals before addresses were lowercased can still be edited.proxy_record_idis now a UUID field, so a malformed value returns a 400 withattrinstead of a 500.eval_email_domain_setup, checks that an agent with the skill sets up a domain correctly. Details below.param_overrides.<field>.requirednow also works together withinput_schema. Before, it was silently ignored.Evals
The suite runs a real agent with the skill against the real email API over MCP. SES, DNS and Domain Connect are simulated per case, so each case decides which records already exist.
--trialsabove 1 for the same reason. Trials of a case share its domain.workflows-email-domain-agent-setupfor its MCP server, so evals can reach the tools.Note
Turn the flag on only after this PR has deployed to Django. The MCP server deploys separately, and until Django has the change every tool call returns 403. The flag must exist in PostHog before the tools appear; an unknown flag hides them and denies token access.
Nothing user-visible changes in the web app.
workflows-email-domain-agent-setup: Default-off server guard for new token authorization; MCP tools use the same flag. Existing session apply-url access remains available.Scoped credentials only support email Domain Connect. Session email and proxy access does not depend on the agent flag.
How did you test this code?
Current test seams: parameterized HTTP requests with PAK, OAuth and session credentials, both flag states, and a mocked signing boundary.
Before: scoped proxy credentials reached signing, and flag-off sessions received 404. After: scoped proxy calls return 403; baseline sessions succeed.
Local proof: agent-final-backend-green.log, agent-final-mcp-tests.log, agent-final-openapi-green.log, agent-final-typecheck.log and agent-final-preflight.log.
62 focused backend HTTP/domain/scorer/simulated-eval tests passed; domain-109703-gate-green.log.
The session flag-off test now preserves baseline 200 behavior. PAK/OAuth proxy tests failed before the fix and pass with 403.
268 MCP generator/apply-url/tool-filtering tests and MCP TypeScript passed; domain-109703-mcp-tests.log and domain-109703-typecheck.log.
Full OpenAPI regeneration and skills lint passed; domain-109703-openapi.log and domain-109703-skills.log.
Strict ci:preflight passed; domain-109703-preflight.log.
Prior validation retained from the original PR (2026-10-06)
Test rationale:
test_email_domain_actions_with_scoped_api_key(12 parameterized cases,posthog/api/test/test_integration.py): fails if an action drops out of, or moves between, the scope lists, if a member's read key loses the check, or if a response body changes shape. Removingemail_verifyfrom the write list fails 2 cases, and removing the kind guard fails the non-email case. Theapply-urlcase also guards that the endpoint validates through the new serializer. It extends the existing API key test class; no existing test called these actions with a key. Theapply-urlcases guard 404 for unknown or non-email integrations, and 403 for scoped proxy access. One case guards the 400 on an address change. The cases run with the flag on.test_email_domain_actions_reject_api_keys_while_the_agent_setup_flag_is_off(4 cases): fails if any of the four actions lets a write-scoped key through with the flag off.test_email_update_from_the_app_keeps_the_sender_address(2 cases): fails if editing a sender stored asHello@…returns 400, which the first version of the address check did. It also fails if the address check runs with the flag off.test_integration_domain_serializers.py(SimpleTestCase, no database): per-context required fields on apply-url, a malformedproxy_record_id, andmaildevrejected outsideDEBUG. TheDEBUGcheck moved from inline choices intovalidate_provider, so it needed its own guard.generate-tools.test.ts: the existingrequiredoverride test became a parameterized case that also coversinput_schema.products/workflows/evals/test/test_simulated_email_domains.py: runs the real SES provider and Domain Connect code on the simulation. It fails if verify stops following published records, if the claimed-domain guard stops firing, or if a case can no longer reach an apply URL through the API.products/workflows/package.jsonnow includesevals/test/inbackend:test, so CI runs it.products/workflows/evals/test/test_scorers.py: the SPF scorer rejects two separate records and an include afterall. The record value scorer rejects a case-changed or truncated TXT token, because TXT values are case-sensitive.integrations-domain-connect-apply-url.test.ts(MCP unit test): fails if the tool forwardscontext: proxyor aproxy_record_idan agent passes.test_sandboxed_harness_services.py: the existing override test now also asserts the email domain flag.Ran locally, on an isolated test database:
posthog/api/test/test_integration.pyin full, the serializer, Domain Connect and eval tests, the eval harness tests, the MCP unit suite, and repo-wide mypy.Not run: the eval suite itself, because this devbox has no
BRAINTRUST_API_KEYorLLM_GATEWAY_ANTHROPIC_API_KEY. Also not run: a live MCP session against a running stack, real SES, or a real Domain Connect approval.To run the suite:
flox activate -- bash -c "hogli evals eval_email_domain_setup".This PR comes from a fork, so CI needs a maintainer to approve the runs.
Release status
Fork handoff only. This task does not activate rollout flags. Deployment flag state was not inspected. Upstream CI and the unavailable fresh Opus review remain handoff gaps.
Docs update
Updated the existing OAuth development guide with email-only Domain Connect access for scoped credentials and baseline session access.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (
claude-opus-5-5[1m])GET email/statusendpoint; whoever lands second rerunshogli build:openapi./improving-drf-endpoints,/implementing-mcp-tools,/writing-tests,/writing-skills,/writing-evals,/running-ci-preflight,/reviewing-with-coderabbit,/writing-pr-descriptions,/tdd,/qa-swarm.kind, the update tool leftconfigoptional, and the skill reused a sender with a different address on the same domain.@hostname missing from the help text, wrong permission levels in the skill, a 500 on non-email ids, and a test that only ran as admin and never read bodies.all, a substring check that failed a correct DMARC answer, a shared discovery cache, an avoided-tool check on a tool the agent cannot see, a judge that penalized the SPF reminder the skill asks for, a member case that never checked the created sender, and DNS state the prompts describe but nobody seeded. Not changed: scorer tests underevals/test/, which follows the notebooks product.proxy_record_idto a UUID in the generated zod schema.apply-urlerrors above, a scorer that ignored case in TXT values, a private scorer entry point in tests, the record type label that left out SPF, and three skill gaps (a DMARC lookup timeout read as a missing record, no source for the sender name on update, a member continuing after a 403). Rejected: per-team tenant scoping in the SES simulation, which is documented and exercised on purpose.apply-urlto the write scope also opens its reverse proxy branch tointegration:writekeys. That earlier decision is superseded: the current fix rejects scoped proxy credentials and retains baseline session proxy access.update_native_integration.exclude_paramsper tool. Todayintegrations-email-createremoveskindfrom the shared create body, which only matters if the genericintegrations-createtool is enabled.Modernization used Codex,
gpt-6.1-sol. Skills: modernize-pr, pr, writing-pr-descriptions, writing-voice, merge-risk, unslop. The source replays onto master atb75567aca6cce9f0499e882463fc7e71d65a064c. Original authorship and prior evidence remain above. The required current Opus review could not run because the Claude API rate limit blocked it.Current review fixes and final upstream replay used Codex,
gpt-6.1-sol. The fresh cross-family review remains blocked by the known Claude rate limit.